package net.sourceforge.springframework;

import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
import java.sql.Connection;
import java.util.List;

import org.apache.ibatis.session.Configuration;
import org.apache.ibatis.session.ExecutorType;
import org.apache.ibatis.session.SqlSession;
import org.apache.ibatis.session.SqlSessionFactory;
import org.apache.ibatis.session.TransactionIsolationLevel;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;


/**
 * override getObject get sql session with safe selectOne
 * 
 * @author alex
 *
 */
public class SqlSessionFactoryBean extends org.mybatis.spring.SqlSessionFactoryBean {
	private final static Logger LOGGER = LoggerFactory.getLogger(SqlSessionFactoryBean.class);

	@Override
	public SqlSessionFactory getObject() throws Exception {
		SqlSessionFactory factory = super.getObject();
		return new ProxySqlSessionFactory(factory);
	}

	class ProxySqlSessionFactory implements SqlSessionFactory {
		private final SqlSessionFactory factory;

		ProxySqlSessionFactory(SqlSessionFactory factory) {
			this.factory = factory;
		}

		public Configuration getConfiguration() {
			return factory.getConfiguration();
		}

		public SqlSession openSession() {
			return dynamicalProxy(factory.openSession());
		}

		public SqlSession openSession(boolean arg0) {
			return dynamicalProxy(factory.openSession(arg0));
		}

		public SqlSession openSession(Connection arg0) {
			return dynamicalProxy(factory.openSession(arg0));
		}

		public SqlSession openSession(TransactionIsolationLevel arg0) {
			return dynamicalProxy(factory.openSession(arg0));
		}

		public SqlSession openSession(ExecutorType arg0) {
			return dynamicalProxy(factory.openSession(arg0));
		}

		public SqlSession openSession(ExecutorType arg0, boolean arg1) {
			return dynamicalProxy(factory.openSession(arg0, arg1));
		}

		public SqlSession openSession(ExecutorType arg0, TransactionIsolationLevel arg1) {
			return dynamicalProxy(factory.openSession(arg0, arg1));
		}

		public SqlSession openSession(ExecutorType arg0, Connection arg1) {
			return dynamicalProxy(factory.openSession(arg0, arg1));
		}
	}

	// proxy selectOne method, not throw exception, just send warnning
	private SqlSession dynamicalProxy(final SqlSession session) {
		return (SqlSession) Proxy.newProxyInstance(session.getClass().getClassLoader(),
				new Class[] { SqlSession.class }, new InvocationHandler() {
					public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
						String methodName = method.getName();
						if ("selectOne".equals(methodName) && args != null && args.length == 2) {
							// Popular vote was to return null on 0 results and throw exception on too many.
							List list = session.selectList(args[0].toString(), args[1]);
							if (list.size() == 1) {
								return list.get(0);
							} else if (list.size() > 1) {
								LOGGER.warn(args[0] + " returned more than one result, it is unsafe selectOne");
								return list.get(0);
							} else {
								return null;
							}
						}
						return method.invoke(session, args);
					}
				});
	}
}
